// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:expect/expect.dart';
import 'package:front_end/src/kernel/member_covariance.dart';
import 'package:kernel/ast.dart';

void main() {
  void checkEquals(Covariance a, Covariance b) {
    Expect.equals(a, b);
    Expect.equals(a.hashCode, b.hashCode);
  }

  checkEquals(const Covariance.empty(), new Covariance.empty());

  Expect.isTrue(const Covariance.empty().isEmpty);
  Expect.isTrue(new Covariance.empty().isEmpty);
  Expect.isTrue(new Covariance.internal(null, null, null).isEmpty);

  checkEquals(
    const Covariance.empty(),
    new Covariance.internal(null, null, null),
  );

  Expect.throws(() => new Covariance.internal([], null, null));
  Expect.throws(() => new Covariance.internal([0], null, null));

  checkEquals(
    new Covariance.internal([Covariance.GenericCovariantImpl], null, null),
    new Covariance.internal([Covariance.GenericCovariantImpl, 0], null, null),
  );

  checkEquals(
    new Covariance.internal([Covariance.Covariant], null, null),
    new Covariance.internal([Covariance.Covariant, 0], null, null),
  );

  checkEquals(
    new Covariance.internal([0, Covariance.Covariant], null, null),
    new Covariance.internal([0, Covariance.Covariant, 0], null, null),
  );

  Expect.throws(() => new Covariance.internal(null, {}, null));
  Expect.throws(() => new Covariance.internal(null, {'a': 0}, null));

  checkEquals(
    new Covariance.internal(null, {'a': Covariance.Covariant}, null),
    new Covariance.internal(null, {'a': Covariance.Covariant}, null),
  );

  Expect.throws(() => new Covariance.internal(null, null, []));

  Expect.throws(() => new Covariance.internal(null, null, [false]));

  checkEquals(
    new Covariance.internal(null, null, [true]),
    new Covariance.internal(null, null, [true, false]),
  );

  Covariance covariance = new Covariance.internal(
    [
      Covariance.Covariant,
      Covariance.GenericCovariantImpl,
      0,
      Covariance.Covariant | Covariance.GenericCovariantImpl,
    ],
    {
      'a': Covariance.Covariant,
      'b': Covariance.GenericCovariantImpl,
      'd': Covariance.Covariant | Covariance.GenericCovariantImpl,
    },
    [false, true],
  );

  Expect.equals(Covariance.Covariant, covariance.getPositionalVariance(0));
  Expect.equals(
    Covariance.GenericCovariantImpl,
    covariance.getPositionalVariance(1),
  );
  Expect.equals(0, covariance.getPositionalVariance(2));
  Expect.equals(
    Covariance.Covariant | Covariance.GenericCovariantImpl,
    covariance.getPositionalVariance(3),
  );
  Expect.equals(0, covariance.getPositionalVariance(4));

  Expect.equals(Covariance.Covariant, covariance.getNamedVariance('a'));
  Expect.equals(
    Covariance.GenericCovariantImpl,
    covariance.getNamedVariance('b'),
  );
  Expect.equals(0, covariance.getNamedVariance('c'));
  Expect.equals(
    Covariance.Covariant | Covariance.GenericCovariantImpl,
    covariance.getNamedVariance('d'),
  );
  Expect.equals(0, covariance.getNamedVariance('e'));

  Expect.isFalse(covariance.isTypeParameterGenericCovariantImpl(0));
  Expect.isTrue(covariance.isTypeParameterGenericCovariantImpl(1));
  Expect.isFalse(covariance.isTypeParameterGenericCovariantImpl(0));

  Expect.stringEquals(
    'Covariance('
    '0:Covariant,1:GenericCovariantImpl,3:GenericCovariantImpl+Covariant,'
    'a:Covariant,b:GenericCovariantImpl,d:GenericCovariantImpl+Covariant,'
    'types:1)',
    covariance.toString(),
  );

  Procedure noParameterProcedure = new Procedure(
    new Name('foo'),
    ProcedureKind.Method,
    new FunctionNode(null),
    fileUri: dummyUri,
  );
  Covariance noParameterProcedureCovariance = new Covariance.fromMember(
    noParameterProcedure,
    forSetter: false,
  );
  Expect.isTrue(noParameterProcedureCovariance.isEmpty);

  covariance.applyCovariance(noParameterProcedure);
  noParameterProcedureCovariance = new Covariance.fromMember(
    noParameterProcedure,
    forSetter: false,
  );
  Expect.isTrue(noParameterProcedureCovariance.isEmpty);

  Procedure oneParameterProcedure = new Procedure(
    new Name('foo'),
    ProcedureKind.Method,
    new FunctionNode(
      null,
      positionalParameters: [
        new VariableDeclaration(null, isSynthesized: true),
      ],
    ),
    fileUri: dummyUri,
  );
  Covariance oneParameterProcedureCovariance = new Covariance.fromMember(
    oneParameterProcedure,
    forSetter: false,
  );
  Expect.isTrue(oneParameterProcedureCovariance.isEmpty);

  covariance.applyCovariance(oneParameterProcedure);
  oneParameterProcedureCovariance = new Covariance.fromMember(
    oneParameterProcedure,
    forSetter: false,
  );
  Expect.isFalse(oneParameterProcedureCovariance.isEmpty);
  Expect.equals(
    new Covariance.internal([Covariance.Covariant], null, null),
    oneParameterProcedureCovariance,
  );

  Procedure positionalParametersProcedure = new Procedure(
    new Name('foo'),
    ProcedureKind.Method,
    new FunctionNode(
      null,
      positionalParameters: [
        new VariableDeclaration(null, isSynthesized: true),
        new VariableDeclaration(null, isSynthesized: true),
        new VariableDeclaration(null, isSynthesized: true),
        new VariableDeclaration(null, isSynthesized: true),
        new VariableDeclaration(null, isSynthesized: true),
      ],
    ),
    fileUri: dummyUri,
  );
  Covariance positionalParametersProcedureCovariance =
      new Covariance.fromMember(
        positionalParametersProcedure,
        forSetter: false,
      );
  Expect.isTrue(positionalParametersProcedureCovariance.isEmpty);

  covariance.applyCovariance(positionalParametersProcedure);
  positionalParametersProcedureCovariance = new Covariance.fromMember(
    positionalParametersProcedure,
    forSetter: false,
  );
  Expect.isFalse(positionalParametersProcedureCovariance.isEmpty);
  checkEquals(
    new Covariance.internal(
      [
        Covariance.Covariant,
        Covariance.GenericCovariantImpl,
        0,
        Covariance.Covariant | Covariance.GenericCovariantImpl,
      ],
      null,
      null,
    ),
    positionalParametersProcedureCovariance,
  );

  Procedure namedParametersProcedure = new Procedure(
    new Name('foo'),
    ProcedureKind.Method,
    new FunctionNode(
      null,
      namedParameters: [
        new VariableDeclaration('a'),
        new VariableDeclaration('b'),
        new VariableDeclaration('c'),
        new VariableDeclaration('d'),
        new VariableDeclaration('e'),
      ],
    ),
    fileUri: dummyUri,
  );
  Covariance namedParametersProcedureCovariance = new Covariance.fromMember(
    namedParametersProcedure,
    forSetter: false,
  );
  Expect.isTrue(namedParametersProcedureCovariance.isEmpty);

  covariance.applyCovariance(namedParametersProcedure);
  namedParametersProcedureCovariance = new Covariance.fromMember(
    namedParametersProcedure,
    forSetter: false,
  );
  Expect.isFalse(namedParametersProcedureCovariance.isEmpty);
  checkEquals(
    new Covariance.internal(null, {
      'a': Covariance.Covariant,
      'b': Covariance.GenericCovariantImpl,
      'd': Covariance.Covariant | Covariance.GenericCovariantImpl,
    }, null),
    namedParametersProcedureCovariance,
  );

  Procedure typeParametersProcedure = new Procedure(
    new Name('foo'),
    ProcedureKind.Method,
    new FunctionNode(
      null,
      typeParameters: [
        new TypeParameter(null),
        new TypeParameter(null),
        new TypeParameter(null),
      ],
    ),
    fileUri: dummyUri,
  );
  Covariance typeParametersProcedureCovariance = new Covariance.fromMember(
    typeParametersProcedure,
    forSetter: false,
  );
  Expect.isTrue(typeParametersProcedureCovariance.isEmpty);

  covariance.applyCovariance(typeParametersProcedure);
  typeParametersProcedureCovariance = new Covariance.fromMember(
    typeParametersProcedure,
    forSetter: false,
  );
  Expect.isFalse(typeParametersProcedureCovariance.isEmpty);
  checkEquals(
    new Covariance.internal(null, null, [false, true]),
    typeParametersProcedureCovariance,
  );

  Covariance merged = const Covariance.empty().merge(
    positionalParametersProcedureCovariance,
  );
  checkEquals(positionalParametersProcedureCovariance, merged);
  merged = merged.merge(namedParametersProcedureCovariance);
  checkEquals(
    new Covariance.internal(
      [
        Covariance.Covariant,
        Covariance.GenericCovariantImpl,
        0,
        Covariance.GenericCovariantImpl | Covariance.Covariant,
      ],
      {
        'a': Covariance.Covariant,
        'b': Covariance.GenericCovariantImpl,
        'd': Covariance.Covariant | Covariance.GenericCovariantImpl,
      },
      null,
    ),
    merged,
  );
  merged = merged.merge(typeParametersProcedureCovariance);
  checkEquals(covariance, merged);
}
